package com.dib.neo.bkfs.fs;


import java.io.IOException;
import java.nio.file.DirectoryIteratorException;
import java.nio.file.DirectoryStream;
import java.nio.file.Path;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

class BKDirectoryStream
    implements DirectoryStream<Path> {

  // path to directory when originally opened
  private final BKPath dir;

  // filter (may be null)
  private final DirectoryStream.Filter<? super Path> filter;

  // used to coorindate closing of directory stream
  private final ReentrantReadWriteLock streamLock =
      new ReentrantReadWriteLock(true);

  // indicates if directory stream is open (synchronize on closeLock)
  private volatile boolean isClosed;

  // directory iterator
  private Iterator<Path> iterator;

  /**
   * Initializes a new instance
   */
  BKDirectoryStream(BKPath dir, DirectoryStream.Filter<? super Path> filter) {
    this.dir = dir;
    this.filter = filter;
  }

  protected final BKPath directory() {
    return dir;
  }

  protected final Lock readLock() {
    return streamLock.readLock();
  }

  protected final Lock writeLock() {
    return streamLock.writeLock();
  }

  protected final boolean isOpen() {
    return !isClosed;
  }

  protected final boolean closeImpl() throws IOException {
    if (!isClosed) {
      isClosed = true;

      return true;
    } else {
      return false;
    }
  }

  @Override
  public void close()
      throws IOException {
    writeLock().lock();
    try {
      closeImpl();
    } finally {
      writeLock().unlock();
    }
  }

  protected final Iterator<Path> iterator(DirectoryStream<Path> ds) {
    if (isClosed) {
      throw new IllegalStateException("Directory stream is closed");
    }
    synchronized (this) {
        if (iterator != null) {
            throw new IllegalStateException("Iterator already obtained");
        }
      iterator = new BKDirectoryIterator(ds);
      return iterator;
    }
  }

  @Override
  public Iterator<Path> iterator() {
    return iterator(this);
  }

  /**
   * Iterator implementation
   */
  private class BKDirectoryIterator implements Iterator<Path> {

    private final DirectoryStream<Path> stream;
    private Iterator<String> childrenPath;

    // true when at EOF
    private boolean atEof;

    // next entry to return
    private Path nextEntry;

    BKDirectoryIterator(DirectoryStream<Path> stream) {
      atEof = false;
      this.stream = stream;
      childrenPath = dir.getChildrenPath().iterator();
    }

    // Returns next entry (or null)
    private Path readNextEntry() {
      assert Thread.holdsLock(this);

      Path entry = null;
      while (childrenPath.hasNext()) {
        entry = dir.resolve(childrenPath.next());
        try {
          if (filter == null || filter.accept(entry)) {
            break;
          }
        } catch (IOException ioe) {
          throw new DirectoryIteratorException(ioe);
        }
      }
      return entry;
    }

    @Override
    public synchronized boolean hasNext() {
        if (nextEntry == null && !atEof) {
            nextEntry = readNextEntry();
        }
      return nextEntry != null;
    }

    @Override
    public synchronized Path next() {
      Path result;
      if (nextEntry == null && !atEof) {
        result = readNextEntry();
      } else {
        result = nextEntry;
        nextEntry = null;
      }
        if (result == null) {
            throw new NoSuchElementException();
        }
      return result;
    }

    @Override
    public void remove() {
      throw new UnsupportedOperationException();
    }
  }
}